package com.pocketjourney.tutorial.media;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;

import android.media.MediaPlayer;
import android.os.Handler;
import android.util.Log;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ProgressBar;                                                                               
import android.widget.TextView;

/**
 * MediaPlayer does not yet support streaming from external URLs so this class
 * provides a pseudo-streaming function by downloading the content incrementally
 * & playing as soon as we get enough audio in our temporary storage.
 */
public class StreamingMediaPlayer {

	private static final int INTIAL_KB_BUFFER = 96 * 10 / 8;// assume
															// 96kbps*10secs/8bits
															// per byte

	private TextView textStreamed;

	private ImageButton playButton;

	private Button streamButton;

	private ProgressBar progressBar;

	// Track for display by progressBar
	private long mediaLengthInKb, mediaLengthInSeconds;
	private int totalKbRead = 0;

	// Create Handler to call View updates on the main UI thread.
	private final Handler handler = new Handler();

	private MediaPlayer mediaPlayer;

	private File downloadingMediaFile;

	private boolean isInterrupted;

	public StreamingMediaPlayer(TextView textStreamed, ImageButton playButton,
			Button streamButton, ProgressBar progressBar) {
		this.textStreamed = textStreamed;
		this.playButton = playButton;
		this.streamButton = streamButton;
		this.progressBar = progressBar;
	}

	/**
	 * Progressivly download the media to a temporary location and update the
	 * MediaPlayer as new content becomes available.
	 */
	public void startStreaming(final String mediaUrl, long mediaLengthInKb,
			long mediaLengthInSeconds) throws IOException {

		this.mediaLengthInKb = mediaLengthInKb;
		this.mediaLengthInSeconds = mediaLengthInSeconds;

		Runnable r = new Runnable() {
			public void run() {
				try {
					downloadAudioIncrement(mediaUrl);
				} catch (IOException e) {
					Log.e(getClass().getName(),
							"Unable to initialize the MediaPlaer for fileUrl="
									+ mediaUrl, e);
					return;
				}
			}
		};
		new Thread(r).start();
	}

	/**
	 * Download the url stream to a temporary location and then call the
	 * setDataSource for that local file
	 */
	public void downloadAudioIncrement(String mediaUrl) throws IOException {

		URLConnection cn = new URL(mediaUrl).openConnection();
		cn.connect();
		InputStream stream = cn.getInputStream();
		if (stream == null) {
			Log.e(getClass().getName(),
					"Unable to create InputStream for mediaUrl:" + mediaUrl);
		}
		downloadingMediaFile = File.createTempFile("downloadingMedia", ".dat",new File("/data/data/com.pocketjourney.tutorial/"));
		FileOutputStream out = new FileOutputStream(downloadingMediaFile);
		byte buf[] = new byte[16384];
		int totalBytesRead = 0, incrementalBytesRead = 0;
		do {
			int numread = stream.read(buf);
			if (numread <= 0)
				break;
			out.write(buf, 0, numread);
			totalBytesRead += numread;
			incrementalBytesRead += numread;
			totalKbRead = totalBytesRead / 1000;

			testMediaBuffer();
			fireDataLoadUpdate();
		} while (validateNotInterrupted());

		stream.close();
		if (validateNotInterrupted()) {
			fireDataFullyLoaded();
		}
	}

	private boolean validateNotInterrupted() {
		if (isInterrupted) {
			if (mediaPlayer != null) {
				mediaPlayer.pause();
				// mediaPlayer.release();
			}
			return false;
		} else {
			return true;
		}
	}

	/**
	 * Test whether we need to transfer buffered data to the MediaPlayer.
	 * Interacting with MediaPlayer on non-main UI thread can causes crashes to
	 * so perform this using a Handler.
	 */
	private void testMediaBuffer() {
		Runnable updater = new Runnable() {
			public void run() {
				if (mediaPlayer == null) {
					// Only create the MediaPlayer once we have the minimum
					// buffered data
					if (totalKbRead >= INTIAL_KB_BUFFER) {
						try {
							startMediaPlayer();
						} catch (Exception e) {
							Log.e(getClass().getName(),
									"Error copying buffered conent.", e);
						}
					}
				} else if (mediaPlayer.getDuration()
						- mediaPlayer.getCurrentPosition() <= 1000) {
					// NOTE: The media player has stopped at the end so transfer
					// any existing buffered data
					// We test for < 1second of data because the media player
					// can stop when there is still
					// a few milliseconds of data left to play
					transferBufferToMediaPlayer();
				}
			}
		};
		handler.post(updater);
	}

	private void startMediaPlayer() {
		try {
			File bufferedFile = File.createTempFile("playingMedia", ".dat",new File("/data/data/com.pocketjourney.tutorial/"));
			copyFile(downloadingMediaFile, bufferedFile);

			mediaPlayer = new MediaPlayer();
			mediaPlayer.setDataSource(bufferedFile.getAbsolutePath());
			mediaPlayer.prepare();
			fireDataPreloadComplete();
		} catch (IOException e) {
			Log
					.e(getClass().getName(),
							"Error initializing the MediaPlaer.", e);
			return;
		}
	}

	/**
	 * Transfer buffered data to the MediaPlayer. Interacting with MediaPlayer
	 * on non-main UI thread can causes crashes to so perform this using a
	 * Handler.
	 */
	private void transferBufferToMediaPlayer() {
		try {
			// First determine if we need to restart the player after
			// transferring data...e.g. perhaps the user pressed pause
			boolean wasPlaying = mediaPlayer.isPlaying();
			int curPosition = mediaPlayer.getCurrentPosition();
			mediaPlayer.pause();

			File bufferedFile = File.createTempFile("playingMedia", ".dat",new File("/data/data/com.pocketjourney.tutorial/"));
			copyFile(downloadingMediaFile, bufferedFile);

			mediaPlayer = new MediaPlayer();
			mediaPlayer.setDataSource(bufferedFile.getAbsolutePath());
			// mediaPlayer.setAudioStreamType(AudioSystem.STREAM_MUSIC);
			mediaPlayer.prepare();
			mediaPlayer.seekTo(curPosition);

			// Restart if at end of prior beuffered content or mediaPlayer was
			// previously playing.
			// NOTE: We test for < 1second of data because the media player can
			// stop when there is still
			// a few milliseconds of data left to play
			boolean atEndOfFile = mediaPlayer.getDuration()
					- mediaPlayer.getCurrentPosition() <= 1000;
			if (wasPlaying || atEndOfFile) {
				mediaPlayer.start();
			}
		} catch (Exception e) {
			Log.e(getClass().getName(),
					"Error updating to newly loaded content.", e);
		}
	}

	private void fireDataLoadUpdate() {
		Runnable updater = new Runnable() {
			public void run() {
				textStreamed.setText((CharSequence) (totalKbRead + " Kb read"));
				float loadProgress = ((float) totalKbRead / (float) mediaLengthInKb);
				progressBar.setSecondaryProgress((int) (loadProgress * 100));
			}
		};
		handler.post(updater);
	}

	/**
	 * We have preloaded enough content and started the MediaPlayer so update
	 * the buttons & progress meters.
	 */
	private void fireDataPreloadComplete() {
		Runnable updater = new Runnable() {
			public void run() {
				mediaPlayer.start();
				startPlayProgressUpdater();
				playButton.setEnabled(true);
				// streamButton.setEnabled(false);
			}
		};
		handler.post(updater);
	}

	private void fireDataFullyLoaded() {
		Runnable updater = new Runnable() {
			public void run() {
				transferBufferToMediaPlayer();
				textStreamed.setText((CharSequence) ("Audio full loaded: "
						+ totalKbRead + " Kb read"));
			}
		};
		handler.post(updater);
	}

	public MediaPlayer getMediaPlayer() {
		return mediaPlayer;
	}

	public void startPlayProgressUpdater() {
		float progress = (((float) mediaPlayer.getCurrentPosition() / 1000) / (float) mediaLengthInSeconds);
		progressBar.setProgress((int) (progress * 100));

		if (mediaPlayer.isPlaying()) {
			Runnable notification = new Runnable() {
				public void run() {
					startPlayProgressUpdater();
				}
			};
			handler.postDelayed(notification, 1000);
		}
	}

	public void interrupt() {
		playButton.setEnabled(false);
		isInterrupted = true;
		validateNotInterrupted();
	}
	
	public static void copyFile(File srcFile, File dstFile) throws IOException {
		  dstFile.mkdirs();
		  InputStream input = null;
		  OutputStream output = null;
		  input = new FileInputStream(srcFile);
		  output = new FileOutputStream(dstFile);
		 
		  int DEFAULT_BUFFER_SIZE = 1024 * 4;
		  byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
		  int n = 0;
		  while (-1 != (n = input.read(buffer))) {
		    output.write(buffer, 0, n);
		  }
		  input.close();
		  output.close();
		}
}
